/**
 * 
 */
package wdmsim;

import java.util.Vector;
import multidomain.Domain;
import events.*;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import util.Auxiliar;
import controlPlane.Handler;

/**
 * @author alissonslp
 *
 */
public class TrafficGenerator {

	private EventScheduler scheduler;
	PhysicalTopology pt;
	private TrafficInfo[] trafficTypes;
	private int weightVector[];
	Distribution dist1, dist2, dist3, dist4;
	int totalWeight = 0;
	Element xml;
	int calls;
	double load;
	int maxRate;
	long meanRate, meanArrivalTime, meanHoldingTime;
	
	public static class Pair {
		OXC source;
		OXC destination;
		
		public Pair(OXC src, OXC dst) {
			this.source = src;
			this.destination = dst;
		}
		
		public OXC getSrc() { return this.source; }
		
		public OXC getDst() { return this.destination; }
	}
	
	public TrafficGenerator() throws Exception {
		xml = (Element) Simulator.DOC.getElementsByTagName("traffic").item(0);
		scheduler = EventScheduler.getInstance();
		pt = PhysicalTopology.getInstance();
		calls = Integer.parseInt(xml.getAttribute("calls"));
		maxRate = Integer.parseInt(xml.getAttribute("max-rate"));
		load = Simulator.LOAD;
		if(load==0) {
			load = Double.parseDouble(xml.getAttribute("load"));
		}
		meanRate = 0; meanArrivalTime = 0; meanHoldingTime = 0;
		NodeList callslist = xml.getElementsByTagName("calls");
		int callsTypes = callslist.getLength();
		int aux=0, rate, weight;
		long holdingTime;
		trafficTypes = new TrafficInfo[callsTypes];
		
		for (int i = 0; i < callsTypes; i++) {
            totalWeight += Integer.parseInt(((Element) callslist.item(i)).getAttribute("weight"));
        }
		
		for (int i = 0; i < callsTypes; i++) {
            // <calls holding-time="1" rate="52" weight="300"/>
        	// rate and cos are fixed choosen; holding-time is choosen according to an exponential distribution
            holdingTime = Long.parseLong(((Element) callslist.item(i)).getAttribute("holding-time"))*Constants.SECOND;
            rate = Integer.parseInt(((Element) callslist.item(i)).getAttribute("rate"));
            weight = Integer.parseInt(((Element) callslist.item(i)).getAttribute("weight"));
            meanRate += ((long) rate * (long) weight) / (long) totalWeight;
            meanHoldingTime += (holdingTime * (long) weight) / (long) totalWeight;
            trafficTypes[i] = new TrafficInfo(holdingTime, rate, weight);
            
            if (Simulator.VERBOSE) {
                System.out.println("#################################");
                System.out.println("Weight: " + Integer.toString(weight) + ".");
                System.out.println("Rate: " + Integer.toString(rate) + "Mbps.");
                System.out.println("Mean holding time: " + Long.toString(holdingTime/1000000) + " seconds.");
            }
        }
		//Simulator.SIMULATION_TOTAL_TIME = ((long)calls*(int)meanHoldingTime*meanRate) / (long)(load*maxRate);
		Simulator.SIMULATION_TOTAL_TIME = ((long)calls*(int)meanHoldingTime) / (long)load;
		weightVector = new int[totalWeight];
		for (int i = 0; i < callsTypes; i++) {
            for (int j = 0; j < trafficTypes[i].getWeight(); j++) {
                weightVector[aux] = i;
                aux++;
            }
        }
		
		dist1 = new Distribution(1, Simulator.SEED);
        dist2 = new Distribution(2, Simulator.SEED);
        dist3 = new Distribution(3, Simulator.SEED);
        dist4 = new Distribution(4, Simulator.SEED);
        
        ///*
		if(Simulator.IS_MULTIDOMAIN) {
			// Cada dominio gera o trafego com origem nele
			for(int i=0; i<pt.getNumDomains(); i++) {
				generateIntradomainTraffic(i);
			}
			//meanArrivalTime /= pt.getNumDomains(); 
		}		
		generateMultidomainTraffic();
		//generateMultidomainPairsTraffic();
        //*/
	
		// traffic generation for debug only
		/* 
		int src, dst, type;
		 
		RouteRequestEvent routeRequestEvent;
		long meanArrivalTime = 100*Constants.MILLI_SECOND;
		Flow flow;
		long time = 0*Constants.SECOND;
		
		for(int i=0; i<calls; i++) {
			src = dst = dist2.nextInt(pt.getNumNodes());
			while (src == dst) {
		        dst = dist2.nextInt(pt.getNumNodes());
		    }
			type = weightVector[dist1.nextInt(totalWeight)];
			flow = new Flow(src, dst, trafficTypes[type].getRate(), trafficTypes[type].getHoldingTime(), 0);
			routeRequestEvent = new RouteRequestEvent(flow, pt.getNode(src));
			time += dist3.nextExponential(meanArrivalTime);				
			
			routeRequestEvent.setHandler((Handler)pt.getDomainFromNode(src).getPce());
			
			scheduler.schedule(routeRequestEvent, time);
		}*/
		// end traffic generation for debug
	}
	

	/**
	 * 
	 */
	private void generateMultidomainTraffic() {
		
		int src, dst, type;
		RouteRequestEvent routeRequestEvent;
		Flow flow;
		long time = 0*Constants.SECOND;
		
		if (load == 0) {
			load = Integer.parseInt(xml.getAttribute("load"));
		}
		//meanArrivalTime = (long)(((double)meanHoldingTime * ((double)meanRate / (double) maxRate)) / (double)load);
		meanArrivalTime = (long)((double)meanHoldingTime / (double)load);
		
		try {
			for(int i=0; i<calls; i++) {
				//src = dst = dist2.nextInt(pt.getNumNodes());
				do {
					src = dist2.nextInt(pt.getNumNodes());
				} while(pt.isBorderNode(src));
				
				do {
		            dst = dist2.nextInt(pt.getNumNodes());
		        } while (src==dst || pt.isBorderNode(dst));
				
				type = weightVector[dist1.nextInt(totalWeight)];
				flow = new Flow(src, dst, trafficTypes[type].getRate(), trafficTypes[type].getHoldingTime(), 0);
				routeRequestEvent = new RouteRequestEvent(flow, pt.getNode(src));
				time += dist3.nextExponential(meanArrivalTime);				
				
				if(Simulator.RWA_MODE.matches("pce-based")) {
					routeRequestEvent.setHandler((Handler)pt.getDomainFromNode(src).getPce());
					
				} else {
					routeRequestEvent.setHandler((Handler)pt.getDomainFromNode(src).getOBGPSPeaker());
				}
				
				scheduler.schedule(routeRequestEvent, time);
			}
			Simulator.SIMULATION_TOTAL_TIME = time;
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
	}
	
	private void generateMultidomainPairsTraffic() throws Exception {
		int numSrc = pt.getNumDomains()/3;
		int numDst = 2*pt.getNumDomains()/3;
		OXC trafficSrc[] = new OXC[numSrc];
		OXC trafficDst[] = new OXC[numDst];
		int src, dst, type;
		RouteRequestEvent routeRequestEvent;
		Flow flow;
		long time = 0*Constants.SECOND;
		Domain domainArray[] = pt.getDomainVector();
		Vector<Domain> domainVector = new Vector<Domain>();
		for(int i=0; i<domainArray.length; i++) {
			domainVector.add(domainArray[i]);
		}
		int k = 0;
		Vector<OXC> nodes;
		// cria um array de transmissores, trafficSrc
		do {
			Domain domain = domainVector.get(0 + (int)(Math.random() * ((domainVector.size()-1 - 0) + 1)));
			nodes = domain.getNodes();
			OXC node = nodes.get((0 + (int)(Math.random() * ((nodes.size()-1 - 0) + 1))));
			if(!pt.isBorderNode(node.getId()) && !Auxiliar.contains(trafficSrc, node)) {
				trafficSrc[k++] = node;
				domainVector.remove(domain);
			}			
		} while(k<numSrc);
		
		// cria um array de receptores, trafficDst
		k = 0;
		do {
			Domain domain = domainVector.get(0 + (int)(Math.random() * ((domainVector.size()-1 - 0) + 1)));
			nodes = domain.getNodes();
			OXC node = nodes.get((0 + (int)(Math.random() * ((nodes.size()-1 - 0) + 1))));			
			
			if(!Auxiliar.contains(trafficSrc, node) && !pt.isBorderNode(node.getId())) {
				trafficDst[k++] = node;
				domainVector.remove(domain);
			}
		} while(k<numDst && !domainVector.isEmpty());
		
		// cria um vetor com os pares (transmissor, receptor)
		Vector<Pair> transmitionPairs = new Vector<Pair>();
		for(int i=0; i<2; i++) {
			for(int j=0; j<trafficSrc.length; j++) {				
				transmitionPairs.add(new Pair(trafficSrc[j], trafficDst[(i*trafficSrc.length)+j]));
			}
		}
		
		//meanArrivalTime = (long)(((double)meanHoldingTime * ((double)meanRate / (double) maxRate)) / (double)load);
		meanArrivalTime = (long)(((double)meanHoldingTime) / (double)load);
		try {
			for(int i=0; i<calls; i++) {
				Pair pair = transmitionPairs.get(0 + (int)(Math.random() * ((transmitionPairs.size()-1 - 0) + 1)));
				src = pair.getSrc().getId();
				dst = pair.getDst().getId();
				
				type = weightVector[dist1.nextInt(totalWeight)];
				flow = new Flow(src, dst, trafficTypes[type].getRate(), trafficTypes[type].getHoldingTime(), 0);
				routeRequestEvent = new RouteRequestEvent(flow, pt.getNode(src));
				time += dist3.nextExponential(meanArrivalTime);				
				if(Simulator.RWA_MODE.matches("pce-based")) {
					routeRequestEvent.setHandler((Handler)pt.getDomainFromNode(src).getPce());
					
				} else {
					routeRequestEvent.setHandler((Handler)pt.getDomainFromNode(src).getOBGPSPeaker());
				}
				
				scheduler.schedule(routeRequestEvent, time);
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
	
	/**
	 * @param i indice para itera�ao dos dominios
	 */
	private void generateIntradomainTraffic(int i) {		
		Domain domain = pt.getDomain(i);
		int src, dst, type, domainMaxRate = domain.getMaxRate(), domainLoad = domain.getLoad();
		Vector<OXC> nodes = domain.getNodes();
		long time = 0*Constants.SECOND, arrivalTime = 0;
		Event routeRequestEvent;
		Flow flow;
		
		// geraçao propriamente dita
		try {
			
			/*
             * Compute the arrival time
             * 
             * load = meanArrivalRate x holdingTime x bw/maxRate
             * 1/meanArrivalRate = (holdingTime x bw/maxRate)/load
             * meanArrivalTime = (holdingTime x bw/maxRate)/load
             */
            
            //arrivalTime = (long)(((double)meanHoldingTime * ((double)meanRate / (double) maxRate)) / (double)load);
			arrivalTime = (long)(((double)meanHoldingTime) / (double)load);
            
			/*while(time < Simulator.SIMULATION_TOTAL_TIME) {
				src = dst = dist2.nextInt(nodes.firstElement().getId(), nodes.lastElement().getId(),domain.getNumNodes());
				while (src == dst) {
		            dst = dist2.nextInt(nodes.firstElement().getId(), nodes.lastElement().getId(), domain.getNumNodes());
		        }
				type = weightVector[dist1.nextInt(totalWeight)];
				flow = new Flow(src, dst, trafficTypes[type].getRate(), trafficTypes[type].getHoldingTime(), 0);
				routeRequestEvent = new RouteRequestEvent(flow, pt.getNode(src));
				time += dist3.nextExponential(arrivalTime);				
				
				routeRequestEvent.setHandler(pt.getDomainFromNode(src).getPce());
				
				scheduler.schedule(routeRequestEvent, time);
			}*/
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
	
}
